/*
 * Copyright 2015 Google Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.inferred.freebuilder.processor.property;

import static org.inferred.freebuilder.processor.GeneratedTypeSubject.assertThat;
import static org.inferred.freebuilder.processor.model.ClassTypeImpl.INTEGER;
import static org.inferred.freebuilder.processor.model.ClassTypeImpl.STRING;
import static org.inferred.freebuilder.processor.model.GenericTypeElementImpl.newTopLevelGenericType;
import static org.inferred.freebuilder.processor.model.PrimitiveTypeImpl.INT;
import static org.inferred.freebuilder.processor.model.WildcardTypeImpl.wildcardSuper;

import com.google.common.collect.ImmutableMap;

import org.inferred.freebuilder.processor.BuilderFactory;
import org.inferred.freebuilder.processor.Datatype;
import org.inferred.freebuilder.processor.GeneratedBuilder;
import org.inferred.freebuilder.processor.model.GenericTypeElementImpl;
import org.inferred.freebuilder.processor.model.GenericTypeElementImpl.GenericTypeMirrorImpl;
import org.inferred.freebuilder.processor.source.FunctionalType;
import org.inferred.freebuilder.processor.source.QualifiedName;
import org.inferred.freebuilder.processor.source.feature.GuavaLibrary;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

import java.util.Optional;

@RunWith(JUnit4.class)
public class ListSourceTest {

  @Test
  public void test_noGuava() {
    assertThat(builder()).generates(
        "// Autogenerated code. Do not modify.",
        "package com.example;",
        "",
        "import com.example.Person;",
        "import java.util.ArrayList;",
        "import java.util.Arrays;",
        "import java.util.Collection;",
        "import java.util.Collections;",
        "import java.util.List;",
        "import java.util.Objects;",
        "import java.util.Spliterator;",
        "import java.util.function.Consumer;",
        "import java.util.stream.BaseStream;",
        "",
        "/** Auto-generated superclass of {@link Person.Builder}, derived from the API of "
            + "{@link Person}. */",
        "abstract class Person_Builder {",
        "",
        "  /**",
        "   * Creates a new builder using {@code value} as a template.",
        "   *",
        "   * <p>If {@code value} is a partial, the builder will return more partials.",
        "   */",
        "  public static Person.Builder from(Person value) {",
        "    if (value instanceof Rebuildable) {",
        "      return ((Rebuildable) value).toBuilder();",
        "    } else {",
        "      return new Person.Builder().mergeFrom(value);",
        "    }",
        "  }",
        "",
        "  private final ArrayList<String> name = new ArrayList<>();",
        "  private final ArrayList<Integer> age = new ArrayList<>();",
        "",
        "  /**",
        "   * Adds {@code element} to the list to be returned from {@link Person#name()}.",
        "   *",
        "   * @return this {@code Builder} object",
        "   * @throws NullPointerException if {@code element} is null",
        "   */",
        "  public Person.Builder addName(String element) {",
        "    name.add(Objects.requireNonNull(element));",
        "    return (Person.Builder) this;",
        "  }",
        "",
        "  /**",
        "   * Adds each element of {@code elements} to the list to be returned from "
            + "{@link Person#name()}.",
        "   *",
        "   * @return this {@code Builder} object",
        "   * @throws NullPointerException if {@code elements} is null or contains a null element",
        "   */",
        "  public Person.Builder addName(String... elements) {",
        "    return addAllName(Arrays.asList(elements));",
        "  }",
        "",
        "  /**",
        "   * Adds each element of {@code elements} to the list to be returned from "
            + "{@link Person#name()}.",
        "   *",
        "   * @return this {@code Builder} object",
        "   * @throws NullPointerException if {@code elements} is null or contains a null element",
        "   */",
        "  public Person.Builder addAllName(Spliterator<? extends String> elements) {",
        "    if ((elements.characteristics() & Spliterator.SIZED) != 0) {",
        "      long elementsSize = elements.estimateSize();",
        "      if (elementsSize > 0 && elementsSize <= Integer.MAX_VALUE) {",
        "        name.ensureCapacity(name.size() + (int) elementsSize);",
        "      }",
        "    }",
        "    elements.forEachRemaining(this::addName);",
        "    return (Person.Builder) this;",
        "  }",
        "",
        "  /**",
        "   * Adds each element of {@code elements} to the list to be returned from "
            + "{@link Person#name()}.",
        "   *",
        "   * @return this {@code Builder} object",
        "   * @throws NullPointerException if {@code elements} is null or contains a null element",
        "   */",
        "  public Person.Builder addAllName(BaseStream<? extends String, ?> elements) {",
        "    return addAllName(elements.spliterator());",
        "  }",
        "",
        "  /**",
        "   * Adds each element of {@code elements} to the list to be returned from "
            + "{@link Person#name()}.",
        "   *",
        "   * @return this {@code Builder} object",
        "   * @throws NullPointerException if {@code elements} is null or contains a null element",
        "   */",
        "  public Person.Builder addAllName(Iterable<? extends String> elements) {",
        "    return addAllName(elements.spliterator());",
        "  }",
        "",
        "  /**",
        "   * Applies {@code mutator} to the list to be returned from {@link Person#name()}.",
        "   *",
        "   * <p>This method mutates the list in-place. {@code mutator} is a void consumer, so any "
            + "value",
        "   * returned from a lambda will be ignored. Take care not to call pure functions, like "
            + "{@link",
        "   * Collection#stream()}.",
        "   *",
        "   * @return this {@code Builder} object",
        "   * @throws NullPointerException if {@code mutator} is null",
        "   */",
        "  public Person.Builder mutateName(Consumer<? super List<String>> mutator) {",
        "    // If addName is overridden, this method will be updated to delegate to it",
        "    mutator.accept(name);",
        "    return (Person.Builder) this;",
        "  }",
        "",
        "  /**",
        "   * Clears the list to be returned from {@link Person#name()}.",
        "   *",
        "   * @return this {@code Builder} object",
        "   */",
        "  public Person.Builder clearName() {",
        "    name.clear();",
        "    return (Person.Builder) this;",
        "  }",
        "",
        "  /**",
        "   * Returns an unmodifiable view of the list that will be returned by "
            + "{@link Person#name()}.",
        "   * Changes to this builder will be reflected in the view.",
        "   */",
        "  public List<String> name() {",
        "    return Collections.unmodifiableList(name);",
        "  }",
        "",
        "  /**",
        "   * Adds {@code element} to the list to be returned from {@link Person#age()}.",
        "   *",
        "   * @return this {@code Builder} object",
        "   */",
        "  public Person.Builder addAge(int element) {",
        "    age.add(element);",
        "    return (Person.Builder) this;",
        "  }",
        "",
        "  /**",
        "   * Adds each element of {@code elements} to the list to be returned from "
            + "{@link Person#age()}.",
        "   *",
        "   * @return this {@code Builder} object",
        "   */",
        "  public Person.Builder addAge(int... elements) {",
        "    age.ensureCapacity(age.size() + elements.length);",
        "    for (int element : elements) {",
        "      addAge(element);",
        "    }",
        "    return (Person.Builder) this;",
        "  }",
        "",
        "  /**",
        "   * Adds each element of {@code elements} to the list to be returned from "
            + "{@link Person#age()}.",
        "   *",
        "   * @return this {@code Builder} object",
        "   * @throws NullPointerException if {@code elements} is null or contains a null element",
        "   */",
        "  public Person.Builder addAllAge(Spliterator<? extends Integer> elements) {",
        "    if ((elements.characteristics() & Spliterator.SIZED) != 0) {",
        "      long elementsSize = elements.estimateSize();",
        "      if (elementsSize > 0 && elementsSize <= Integer.MAX_VALUE) {",
        "        age.ensureCapacity(age.size() + (int) elementsSize);",
        "      }",
        "    }",
        "    elements.forEachRemaining(this::addAge);",
        "    return (Person.Builder) this;",
        "  }",
        "",
        "  /**",
        "   * Adds each element of {@code elements} to the list to be returned from "
            + "{@link Person#age()}.",
        "   *",
        "   * @return this {@code Builder} object",
        "   * @throws NullPointerException if {@code elements} is null or contains a null element",
        "   */",
        "  public Person.Builder addAllAge(BaseStream<? extends Integer, ?> elements) {",
        "    return addAllAge(elements.spliterator());",
        "  }",
        "",
        "  /**",
        "   * Adds each element of {@code elements} to the list to be returned from "
            + "{@link Person#age()}.",
        "   *",
        "   * @return this {@code Builder} object",
        "   * @throws NullPointerException if {@code elements} is null or contains a null element",
        "   */",
        "  public Person.Builder addAllAge(Iterable<? extends Integer> elements) {",
        "    return addAllAge(elements.spliterator());",
        "  }",
        "",
        "  /**",
        "   * Applies {@code mutator} to the list to be returned from {@link Person#age()}.",
        "   *",
        "   * <p>This method mutates the list in-place. {@code mutator} is a void consumer, so any "
            + "value",
        "   * returned from a lambda will be ignored. Take care not to call pure functions, like "
            + "{@link",
        "   * Collection#stream()}.",
        "   *",
        "   * @return this {@code Builder} object",
        "   * @throws NullPointerException if {@code mutator} is null",
        "   */",
        "  public Person.Builder mutateAge(Consumer<? super List<Integer>> mutator) {",
        "    // If addAge is overridden, this method will be updated to delegate to it",
        "    mutator.accept(age);",
        "    return (Person.Builder) this;",
        "  }",
        "",
        "  /**",
        "   * Clears the list to be returned from {@link Person#age()}.",
        "   *",
        "   * @return this {@code Builder} object",
        "   */",
        "  public Person.Builder clearAge() {",
        "    age.clear();",
        "    return (Person.Builder) this;",
        "  }",
        "",
        "  /**",
        "   * Returns an unmodifiable view of the list that will be returned by "
            + "{@link Person#age()}. Changes",
        "   * to this builder will be reflected in the view.",
        "   */",
        "  public List<Integer> age() {",
        "    return Collections.unmodifiableList(age);",
        "  }",
        "",
        "  /**",
        "   * Copies values from {@code value}, appending to collections.",
        "   *",
        "   * @return this {@code Builder} object",
        "   */",
        "  public Person.Builder mergeFrom(Person value) {",
        "    addAllName(value.name());",
        "    addAllAge(value.age());",
        "    return (Person.Builder) this;",
        "  }",
        "",
        "  /**",
        "   * Copies values from {@code template}, appending to collections.",
        "   *",
        "   * @return this {@code Builder} object",
        "   */",
        "  public Person.Builder mergeFrom(Person.Builder template) {",
        "    // Upcast to access private fields; otherwise, oddly, we get an access violation.",
        "    Person_Builder base = template;",
        "    addAllName(base.name);",
        "    addAllAge(base.age);",
        "    return (Person.Builder) this;",
        "  }",
        "",
        "  /**",
        "   * Resets the state of this builder.",
        "   *",
        "   * @return this {@code Builder} object",
        "   */",
        "  public Person.Builder clear() {",
        "    clearName();",
        "    clearAge();",
        "    return (Person.Builder) this;",
        "  }",
        "",
        "  /** Returns a newly-created {@link Person} based on the contents of this "
            + "{@code Builder}. */",
        "  public Person build() {",
        "    return new Value(this);",
        "  }",
        "",
        "  /**",
        "   * Returns a newly-created partial {@link Person} for use in unit tests. "
            + "State checking will not",
        "   * be performed.",
        "   *",
        "   * <p>The builder returned by {@link Person.Builder#from(Person)} will propagate the "
            + "partial",
        "   * status of its input, overriding {@link Person.Builder#build() build()} to return "
            + "another",
        "   * partial. This allows for robust tests of modify-rebuild code.",
        "   *",
        "   * <p>Partials should only ever be used in tests. "
            + "They permit writing robust test cases that won't",
        "   * fail if this type gains more application-level constraints "
            + "(e.g. new required fields) in",
        "   * future. If you require partially complete values in production code, "
            + "consider using a Builder.",
        "   */",
        "  public Person buildPartial() {",
        "    return new Partial(this);",
        "  }",
        "",
        "  private abstract static class Rebuildable extends Person {",
        "    public abstract Person.Builder toBuilder();",
        "  }",
        "",
        "  private static final class Value extends Rebuildable {",
        "    private final List<String> name;",
        "    private final List<Integer> age;",
        "",
        "    private Value(Person_Builder builder) {",
        "      this.name = immutableList(builder.name);",
        "      this.age = immutableList(builder.age);",
        "    }",
        "",
        "    @Override",
        "    public List<String> name() {",
        "      return name;",
        "    }",
        "",
        "    @Override",
        "    public List<Integer> age() {",
        "      return age;",
        "    }",
        "",
        "    @Override",
        "    public Person.Builder toBuilder() {",
        "      Person_Builder builder = new Person.Builder();",
        "      builder.name.addAll(name);",
        "      builder.age.addAll(age);",
        "      return (Person.Builder) builder;",
        "    }",
        "",
        "    @Override",
        "    public boolean equals(Object obj) {",
        "      if (!(obj instanceof Value)) {",
        "        return false;",
        "      }",
        "      Value other = (Value) obj;",
        "      return Objects.equals(name, other.name) && Objects.equals(age, other.age);",
        "    }",
        "",
        "    @Override",
        "    public int hashCode() {",
        "      return Objects.hash(name, age);",
        "    }",
        "",
        "    @Override",
        "    public String toString() {",
        "      return \"Person{name=\" + name + \", age=\" + age + \"}\";",
        "    }",
        "  }",
        "",
        "  private static final class Partial extends Rebuildable {",
        "    private final List<String> name;",
        "    private final List<Integer> age;",
        "",
        "    Partial(Person_Builder builder) {",
        "      this.name = immutableList(builder.name);",
        "      this.age = immutableList(builder.age);",
        "    }",
        "",
        "    @Override",
        "    public List<String> name() {",
        "      return name;",
        "    }",
        "",
        "    @Override",
        "    public List<Integer> age() {",
        "      return age;",
        "    }",
        "",
        "    private static class PartialBuilder extends Person.Builder {",
        "      @Override",
        "      public Person build() {",
        "        return buildPartial();",
        "      }",
        "    }",
        "",
        "    @Override",
        "    public Person.Builder toBuilder() {",
        "      Person_Builder builder = new PartialBuilder();",
        "      builder.name.addAll(name);",
        "      builder.age.addAll(age);",
        "      return (Person.Builder) builder;",
        "    }",
        "",
        "    @Override",
        "    public boolean equals(Object obj) {",
        "      if (!(obj instanceof Partial)) {",
        "        return false;",
        "      }",
        "      Partial other = (Partial) obj;",
        "      return Objects.equals(name, other.name) && Objects.equals(age, other.age);",
        "    }",
        "",
        "    @Override",
        "    public int hashCode() {",
        "      return Objects.hash(name, age);",
        "    }",
        "",
        "    @Override",
        "    public String toString() {",
        "      return \"partial Person{name=\" + name + \", age=\" + age + \"}\";",
        "    }",
        "  }",
        "",
        "  @SuppressWarnings(\"unchecked\")",
        "  private static <E> List<E> immutableList(List<E> elements) {",
        "    switch (elements.size()) {",
        "      case 0:",
        "        return Collections.emptyList();",
        "      case 1:",
        "        return Collections.singletonList(elements.get(0));",
        "      default:",
        "        return (List<E>) (List<?>) Collections.unmodifiableList("
            + "Arrays.asList(elements.toArray()));",
        "    }",
        "  }",
        "}");
  }

  @Test
  public void test_guava() {
    assertThat(builder()).given(GuavaLibrary.AVAILABLE).generates(
        "// Autogenerated code. Do not modify.",
        "package com.example;",
        "",
        "import com.example.Person;",
        "import com.google.common.annotations.VisibleForTesting;",
        "import com.google.common.collect.ImmutableList;",
        "import com.google.common.primitives.Ints;",
        "import java.util.ArrayList;",
        "import java.util.Arrays;",
        "import java.util.Collection;",
        "import java.util.Collections;",
        "import java.util.List;",
        "import java.util.Objects;",
        "import java.util.Spliterator;",
        "import java.util.function.Consumer;",
        "import java.util.stream.BaseStream;",
        "",
        "/** Auto-generated superclass of {@link Person.Builder}, "
            + "derived from the API of {@link Person}. */",
        "abstract class Person_Builder {",
        "",
        "  /**",
        "   * Creates a new builder using {@code value} as a template.",
        "   *",
        "   * <p>If {@code value} is a partial, the builder will return more partials.",
        "   */",
        "  public static Person.Builder from(Person value) {",
        "    if (value instanceof Rebuildable) {",
        "      return ((Rebuildable) value).toBuilder();",
        "    } else {",
        "      return new Person.Builder().mergeFrom(value);",
        "    }",
        "  }",
        "",
        "  private List<String> name = ImmutableList.of();",
        "  private List<Integer> age = ImmutableList.of();",
        "",
        "  /**",
        "   * Adds {@code element} to the list to be returned from {@link Person#name()}.",
        "   *",
        "   * @return this {@code Builder} object",
        "   * @throws NullPointerException if {@code element} is null",
        "   */",
        "  public Person.Builder addName(String element) {",
        "    if (name instanceof ImmutableList) {",
        "      name = new ArrayList<>(name);",
        "    }",
        "    name.add(Objects.requireNonNull(element));",
        "    return (Person.Builder) this;",
        "  }",
        "",
        "  /**",
        "   * Adds each element of {@code elements} to the list to be returned from "
            + "{@link Person#name()}.",
        "   *",
        "   * @return this {@code Builder} object",
        "   * @throws NullPointerException if {@code elements} is null or contains a null element",
        "   */",
        "  public Person.Builder addName(String... elements) {",
        "    return addAllName(Arrays.asList(elements));",
        "  }",
        "",
        "  /**",
        "   * Adds each element of {@code elements} to the list to be returned from "
            + "{@link Person#name()}.",
        "   *",
        "   * @return this {@code Builder} object",
        "   * @throws NullPointerException if {@code elements} is null or contains a null element",
        "   */",
        "  public Person.Builder addAllName(Spliterator<? extends String> elements) {",
        "    if ((elements.characteristics() & Spliterator.SIZED) != 0) {",
        "      long elementsSize = elements.estimateSize();",
        "      if (elementsSize > 0 && elementsSize <= Integer.MAX_VALUE) {",
        "        if (name instanceof ImmutableList) {",
        "          name = new ArrayList<>(name);",
        "        }",
        "        ((ArrayList<?>) name).ensureCapacity(name.size() + (int) elementsSize);",
        "      }",
        "    }",
        "    elements.forEachRemaining(this::addName);",
        "    return (Person.Builder) this;",
        "  }",
        "",
        "  /**",
        "   * Adds each element of {@code elements} to the list to be returned from "
            + "{@link Person#name()}.",
        "   *",
        "   * @return this {@code Builder} object",
        "   * @throws NullPointerException if {@code elements} is null or contains a null element",
        "   */",
        "  public Person.Builder addAllName(BaseStream<? extends String, ?> elements) {",
        "    return addAllName(elements.spliterator());",
        "  }",
        "",
        "  /**",
        "   * Adds each element of {@code elements} to the list to be returned from "
            + "{@link Person#name()}.",
        "   *",
        "   * @return this {@code Builder} object",
        "   * @throws NullPointerException if {@code elements} is null or contains a null element",
        "   */",
        "  public Person.Builder addAllName(Iterable<? extends String> elements) {",
        "    return addAllName(elements.spliterator());",
        "  }",
        "",
        "  /**",
        "   * Applies {@code mutator} to the list to be returned from {@link Person#name()}.",
        "   *",
        "   * <p>This method mutates the list in-place. {@code mutator} is a void consumer, so any "
            + "value",
        "   * returned from a lambda will be ignored. Take care not to call pure functions, like "
            + "{@link",
        "   * Collection#stream()}.",
        "   *",
        "   * @return this {@code Builder} object",
        "   * @throws NullPointerException if {@code mutator} is null",
        "   */",
        "  public Person.Builder mutateName(Consumer<? super List<String>> mutator) {",
        "    if (name instanceof ImmutableList) {",
        "      name = new ArrayList<>(name);",
        "    }",
        "    // If addName is overridden, this method will be updated to delegate to it",
        "    mutator.accept(name);",
        "    return (Person.Builder) this;",
        "  }",
        "",
        "  /**",
        "   * Clears the list to be returned from {@link Person#name()}.",
        "   *",
        "   * @return this {@code Builder} object",
        "   */",
        "  public Person.Builder clearName() {",
        "    if (name instanceof ImmutableList) {",
        "      name = ImmutableList.of();",
        "    } else {",
        "      name.clear();",
        "    }",
        "    return (Person.Builder) this;",
        "  }",
        "",
        "  /**",
        "   * Returns an unmodifiable view of the list that will be returned by "
            + "{@link Person#name()}.",
        "   * Changes to this builder will be reflected in the view.",
        "   */",
        "  public List<String> name() {",
        "    if (name instanceof ImmutableList) {",
        "      name = new ArrayList<>(name);",
        "    }",
        "    return Collections.unmodifiableList(name);",
        "  }",
        "",
        "  /**",
        "   * Adds {@code element} to the list to be returned from {@link Person#age()}.",
        "   *",
        "   * @return this {@code Builder} object",
        "   */",
        "  public Person.Builder addAge(int element) {",
        "    if (age instanceof ImmutableList) {",
        "      age = new ArrayList<>(age);",
        "    }",
        "    age.add(element);",
        "    return (Person.Builder) this;",
        "  }",
        "",
        "  /**",
        "   * Adds each element of {@code elements} to the list to be returned from "
            + "{@link Person#age()}.",
        "   *",
        "   * @return this {@code Builder} object",
        "   */",
        "  public Person.Builder addAge(int... elements) {",
        "    return addAllAge(Ints.asList(elements));",
        "  }",
        "",
        "  /**",
        "   * Adds each element of {@code elements} to the list to be returned from "
            + "{@link Person#age()}.",
        "   *",
        "   * @return this {@code Builder} object",
        "   * @throws NullPointerException if {@code elements} is null or contains a null element",
        "   */",
        "  public Person.Builder addAllAge(Spliterator<? extends Integer> elements) {",
        "    if ((elements.characteristics() & Spliterator.SIZED) != 0) {",
        "      long elementsSize = elements.estimateSize();",
        "      if (elementsSize > 0 && elementsSize <= Integer.MAX_VALUE) {",
        "        if (age instanceof ImmutableList) {",
        "          age = new ArrayList<>(age);",
        "        }",
        "        ((ArrayList<?>) age).ensureCapacity(age.size() + (int) elementsSize);",
        "      }",
        "    }",
        "    elements.forEachRemaining(this::addAge);",
        "    return (Person.Builder) this;",
        "  }",
        "",
        "  /**",
        "   * Adds each element of {@code elements} to the list to be returned from "
            + "{@link Person#age()}.",
        "   *",
        "   * @return this {@code Builder} object",
        "   * @throws NullPointerException if {@code elements} is null or contains a null element",
        "   */",
        "  public Person.Builder addAllAge(BaseStream<? extends Integer, ?> elements) {",
        "    return addAllAge(elements.spliterator());",
        "  }",
        "",
        "  /**",
        "   * Adds each element of {@code elements} to the list to be returned from "
            + "{@link Person#age()}.",
        "   *",
        "   * @return this {@code Builder} object",
        "   * @throws NullPointerException if {@code elements} is null or contains a null element",
        "   */",
        "  public Person.Builder addAllAge(Iterable<? extends Integer> elements) {",
        "    return addAllAge(elements.spliterator());",
        "  }",
        "",
        "  /**",
        "   * Applies {@code mutator} to the list to be returned from {@link Person#age()}.",
        "   *",
        "   * <p>This method mutates the list in-place. {@code mutator} is a void consumer, so any "
            + "value",
        "   * returned from a lambda will be ignored. Take care not to call pure functions, like "
            + "{@link",
        "   * Collection#stream()}.",
        "   *",
        "   * @return this {@code Builder} object",
        "   * @throws NullPointerException if {@code mutator} is null",
        "   */",
        "  public Person.Builder mutateAge(Consumer<? super List<Integer>> mutator) {",
        "    if (age instanceof ImmutableList) {",
        "      age = new ArrayList<>(age);",
        "    }",
        "    // If addAge is overridden, this method will be updated to delegate to it",
        "    mutator.accept(age);",
        "    return (Person.Builder) this;",
        "  }",
        "",
        "  /**",
        "   * Clears the list to be returned from {@link Person#age()}.",
        "   *",
        "   * @return this {@code Builder} object",
        "   */",
        "  public Person.Builder clearAge() {",
        "    if (age instanceof ImmutableList) {",
        "      age = ImmutableList.of();",
        "    } else {",
        "      age.clear();",
        "    }",
        "    return (Person.Builder) this;",
        "  }",
        "",
        "  /**",
        "   * Returns an unmodifiable view of the list that will be returned by "
            + "{@link Person#age()}. Changes",
        "   * to this builder will be reflected in the view.",
        "   */",
        "  public List<Integer> age() {",
        "    if (age instanceof ImmutableList) {",
        "      age = new ArrayList<>(age);",
        "    }",
        "    return Collections.unmodifiableList(age);",
        "  }",
        "",
        "  /**",
        "   * Copies values from {@code value}, appending to collections.",
        "   *",
        "   * @return this {@code Builder} object",
        "   */",
        "  public Person.Builder mergeFrom(Person value) {",
        "    if (value instanceof Value && name == ImmutableList.<String>of()) {",
        "      name = ImmutableList.copyOf(value.name());",
        "    } else {",
        "      addAllName(value.name());",
        "    }",
        "    if (value instanceof Value && age == ImmutableList.<Integer>of()) {",
        "      age = ImmutableList.copyOf(value.age());",
        "    } else {",
        "      addAllAge(value.age());",
        "    }",
        "    return (Person.Builder) this;",
        "  }",
        "",
        "  /**",
        "   * Copies values from {@code template}, appending to collections.",
        "   *",
        "   * @return this {@code Builder} object",
        "   */",
        "  public Person.Builder mergeFrom(Person.Builder template) {",
        "    // Upcast to access private fields; otherwise, oddly, we get an access violation.",
        "    Person_Builder base = template;",
        "    addAllName(base.name);",
        "    addAllAge(base.age);",
        "    return (Person.Builder) this;",
        "  }",
        "",
        "  /**",
        "   * Resets the state of this builder.",
        "   *",
        "   * @return this {@code Builder} object",
        "   */",
        "  public Person.Builder clear() {",
        "    clearName();",
        "    clearAge();",
        "    return (Person.Builder) this;",
        "  }",
        "",
        "  /** Returns a newly-created {@link Person} based on the contents of this "
            + "{@code Builder}. */",
        "  public Person build() {",
        "    return new Value(this);",
        "  }",
        "",
        "  /**",
        "   * Returns a newly-created partial {@link Person} for use in unit tests. "
            + "State checking will not",
        "   * be performed.",
        "   *",
        "   * <p>The builder returned by {@link Person.Builder#from(Person)} will propagate the "
            + "partial",
        "   * status of its input, overriding {@link Person.Builder#build() build()} to return "
            + "another",
        "   * partial. This allows for robust tests of modify-rebuild code.",
        "   *",
        "   * <p>Partials should only ever be used in tests. "
            + "They permit writing robust test cases that won't",
        "   * fail if this type gains more application-level constraints "
            + "(e.g. new required fields) in",
        "   * future. If you require partially complete values in production code, "
            + "consider using a Builder.",
        "   */",
        "  @VisibleForTesting()",
        "  public Person buildPartial() {",
        "    return new Partial(this);",
        "  }",
        "",
        "  private abstract static class Rebuildable extends Person {",
        "    public abstract Person.Builder toBuilder();",
        "  }",
        "",
        "  private static final class Value extends Rebuildable {",
        "    private final ImmutableList<String> name;",
        "    private final ImmutableList<Integer> age;",
        "",
        "    private Value(Person_Builder builder) {",
        "      this.name = ImmutableList.copyOf(builder.name);",
        "      this.age = ImmutableList.copyOf(builder.age);",
        "    }",
        "",
        "    @Override",
        "    public List<String> name() {",
        "      return name;",
        "    }",
        "",
        "    @Override",
        "    public List<Integer> age() {",
        "      return age;",
        "    }",
        "",
        "    @Override",
        "    public Person.Builder toBuilder() {",
        "      Person_Builder builder = new Person.Builder();",
        "      builder.name = name;",
        "      builder.age = age;",
        "      return (Person.Builder) builder;",
        "    }",
        "",
        "    @Override",
        "    public boolean equals(Object obj) {",
        "      if (!(obj instanceof Value)) {",
        "        return false;",
        "      }",
        "      Value other = (Value) obj;",
        "      return Objects.equals(name, other.name) && Objects.equals(age, other.age);",
        "    }",
        "",
        "    @Override",
        "    public int hashCode() {",
        "      return Objects.hash(name, age);",
        "    }",
        "",
        "    @Override",
        "    public String toString() {",
        "      return \"Person{name=\" + name + \", age=\" + age + \"}\";",
        "    }",
        "  }",
        "",
        "  private static final class Partial extends Rebuildable {",
        "    private final ImmutableList<String> name;",
        "    private final ImmutableList<Integer> age;",
        "",
        "    Partial(Person_Builder builder) {",
        "      this.name = ImmutableList.copyOf(builder.name);",
        "      this.age = ImmutableList.copyOf(builder.age);",
        "    }",
        "",
        "    @Override",
        "    public List<String> name() {",
        "      return name;",
        "    }",
        "",
        "    @Override",
        "    public List<Integer> age() {",
        "      return age;",
        "    }",
        "",
        "    private static class PartialBuilder extends Person.Builder {",
        "      @Override",
        "      public Person build() {",
        "        return buildPartial();",
        "      }",
        "    }",
        "",
        "    @Override",
        "    public Person.Builder toBuilder() {",
        "      Person_Builder builder = new PartialBuilder();",
        "      builder.name = name;",
        "      builder.age = age;",
        "      return (Person.Builder) builder;",
        "    }",
        "",
        "    @Override",
        "    public boolean equals(Object obj) {",
        "      if (!(obj instanceof Partial)) {",
        "        return false;",
        "      }",
        "      Partial other = (Partial) obj;",
        "      return Objects.equals(name, other.name) && Objects.equals(age, other.age);",
        "    }",
        "",
        "    @Override",
        "    public int hashCode() {",
        "      return Objects.hash(name, age);",
        "    }",
        "",
        "    @Override",
        "    public String toString() {",
        "      return \"partial Person{name=\" + name + \", age=\" + age + \"}\";",
        "    }",
        "  }",
        "}");
  }

  /**
   * Returns a {@link Datatype} instance for a FreeBuilder type with two properties: name, of
   * type {@code List<String>}; and age, of type {@code List<Integer>}.
   */
  private static GeneratedBuilder builder() {
    GenericTypeElementImpl list = newTopLevelGenericType("java.util.List");
    GenericTypeMirrorImpl listInteger = list.newMirror(INTEGER);
    GenericTypeMirrorImpl listString = list.newMirror(STRING);
    QualifiedName person = QualifiedName.of("com.example", "Person");
    QualifiedName generatedBuilder = QualifiedName.of("com.example", "Person_Builder");

    Datatype datatype = new Datatype.Builder()
        .setBuilder(person.nestedType("Builder").withParameters())
        .setExtensible(true)
        .setBuilderFactory(BuilderFactory.NO_ARGS_CONSTRUCTOR)
        .setBuilderSerializable(false)
        .setGeneratedBuilder(generatedBuilder.withParameters())
        .setInterfaceType(false)
        .setPartialType(generatedBuilder.nestedType("Partial").withParameters())
        .setPropertyEnum(generatedBuilder.nestedType("Property").withParameters())
        .setRebuildableType(generatedBuilder.nestedType("Rebuildable").withParameters())
        .setType(person.withParameters())
        .setValueType(generatedBuilder.nestedType("Value").withParameters())
        .build();
    Property name = new Property.Builder()
        .setAllCapsName("NAME")
        .setBoxedType(listString)
        .setCapitalizedName("Name")
        .setFullyCheckedCast(true)
        .setGetterName("name")
        .setName("name")
        .setType(listString)
        .setUsingBeanConvention(false)
        .build();
    Property age = new Property.Builder()
        .setAllCapsName("AGE")
        .setBoxedType(listInteger)
        .setCapitalizedName("Age")
        .setFullyCheckedCast(true)
        .setGetterName("age")
        .setName("age")
        .setType(listInteger)
        .setUsingBeanConvention(false)
        .build();

    return new GeneratedBuilder(datatype, ImmutableMap.of(
        name, new ListProperty(
            datatype,
            name,
            false,
            false,
            false,
            STRING,
            Optional.empty(),
            FunctionalType.consumer(wildcardSuper(listString))),
        age, new ListProperty(
            datatype,
            age,
            false,
            false,
            false,
            INTEGER,
            Optional.of(INT),
            FunctionalType.consumer(wildcardSuper(listInteger)))));
  }
}
